
package org.hackystat.ui.sensordataviewer.server;

import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import java.util.jar.Attributes;
import java.util.jar.Manifest;

import javax.xml.datatype.XMLGregorianCalendar;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;

import org.hackystat.sensorbase.client.SensorBaseClient;
import org.hackystat.sensorbase.client.SensorBaseClientException;
import org.hackystat.utilities.tstamp.Tstamp;
import org.hackystat.sensorbase.resource.sensordata.jaxb.Property;
import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorData;
import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorDataIndex;
import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorDataRef;
import org.hackystat.ui.sensordataviewer.client.common.RPCException;
import org.hackystat.ui.sensordataviewer.client.common.RPCRemoteService;

/**
 * Provides remote asynchronous service for fetching sensor data.
 * @author David J. Nickles
 */
public class RPCServiceImpl extends RemoteServiceServlet implements RPCRemoteService {

  /** {@literal} Class variables for serialized object id. */
  private static final long serialVersionUID = 11121936L;
  /** The sensor properties instance. */
  private Properties sensorProperties; 
    
  /**
   * Creates an array of strings for the sensor data instance in the SensorData instance.
   * @param sensordata A JAXB unmarshalled SensorData instance
   * @return String A formatted line of sensor data attributes of sensor data to present in the UI
   * @throws Exception thrown if problems occur during the Sensorbase procedure call
   */
  private ArrayList<String> formatSensordata(SensorData sensordata) throws Exception {
    ArrayList<String> list = new ArrayList<String>();
    list.add(SimpleDateFactories.getInstance().getBodyfactory().format(
      sensordata.getTimestamp().toGregorianCalendar().getTime()) + " " 
      + sensordata.getSensorDataType() + " " + sensordata.getTool());
    list.add("Timestamp=" + sensordata.getTimestamp().toXMLFormat());
    list.add("SensorDataType=" + sensordata.getSensorDataType());
    list.add("Tool=" + sensordata.getTool());
    list.add("LastMod=" + sensordata.getLastMod().toXMLFormat());
    list.add("Runtime=" + sensordata.getRuntime().toXMLFormat());
    list.add("Resource=" + sensordata.getResource());
    list.add("Owner=" + sensordata.getOwner());
    for (Property p : sensordata.getProperties().getProperty()) {
      StringBuffer property = new StringBuffer();
      property.append(p.getKey());
      property.append('=');
      property.append(p.getValue());
      list.add(property.toString());
    }
    return list;
  }

  /**
   * Return a formatted description of the requested sensor data on the specified interval.
   * @param uri the URI for the SensorBase
   * @param project the project of interest
   * @param user user to authentication
   * @param password user password for authentication
   * @param startXmlTime of interval
   * @param endXmlTime of interval
   * @return Boolean array of days with data
   * @throws RPCException if problems occur during the SensorBase procedure call
   */
  public ArrayList<ArrayList<String>> getMod(String uri, String project, String user, 
    String password, String startXmlTime, String endXmlTime) throws RPCException {
    String params = "URI: " + uri + ", Project: " + project + ", User: " + user 
      + ", start: " +  startXmlTime + ", end: " +  endXmlTime + '\n';
    log("SDV getMod: entering method\n" + params);
    Stopwatch stopwatch = new Stopwatch();
    stopwatch.start();  
    EventCollection eventCollection = new EventCollection();
    try {
      XMLGregorianCalendar startTstamp = Tstamp.makeTimestamp(startXmlTime);
      XMLGregorianCalendar endTstamp = Tstamp.makeTimestamp(endXmlTime);
      SensorBaseClient client = new SensorBaseClient(uri, user, password);
      SensorDataIndex index = client.getSensorDataIndexLastMod(user, startTstamp, endTstamp);
      log("SDV getMod: SensorDataIndex retrieved from " + startTstamp.toXMLFormat() + " to " 
        + endTstamp.toXMLFormat() + " with " + index.getSensorDataRef().size() + " items.\n");
      for (SensorDataRef ref : index.getSensorDataRef()) {
        eventCollection.add(new SensorDataItem(ref));
      }
      log("SDV getMod: ArrayList populated with data values.\n");
    } 
    catch (Exception e) {
      log("SDV getMod error: " + e.getMessage());
      ArrayList<String> error = new ArrayList<String>();
      error.add(startXmlTime);
      error.add("Error");
      error.add("Sensorbase");
      error.add(endXmlTime);
      error.add("Name=" + e.getClass().getCanonicalName());
      error.add("Message=" + e.getMessage());
      //result.add(error);
    }
    stopwatch.stop();
    log("SDV getMod: exiting method with execution time of " + stopwatch + '\n');
    return eventCollection.getList(); 
  }
  
  /**
   * Return a formatted description of the requested sensor data on the specified interval.
   * @param host the URI for the SensorBase
   * @param user user to authentication
   * @param password user password for authentication
   * @param hrefList the list of hrefs for the sensor data items
   * @return String Description of the requested sensor data
   */
  public ArrayList<ArrayList<String>> getHrefs(String host, String user, String password, 
      List hrefList) {
    Stopwatch stopwatch = new Stopwatch();
    stopwatch.start();  
    ArrayList<ArrayList<String>> result = new ArrayList<ArrayList<String>>();
    try {
      SensorBaseClient client = new SensorBaseClient(host, user, password);
      for (Object obj : hrefList) {
        String href = "";
        if (href instanceof String) {
          href = (String)obj;
        }
        String xmlTimestamp = "";
        StringTokenizer st = new StringTokenizer(href, "/");
        while (st.hasMoreTokens()) {
          xmlTimestamp = st.nextToken();
        }
        SensorData sd = client.getSensorData(user, Tstamp.makeTimestamp(xmlTimestamp));
        result.add(formatSensordata(sd));
      }
    } 
    catch (Exception e) {
      log("SDV error: " + e.getMessage());
    }
    stopwatch.stop();
    log("SDV getHrefs: exiting method with an execution time of " + stopwatch + '\n');
    return result;
  }
  
  /**
   * Returns true if the passed host is a SensorBase host.
   * 
   * @param host the URI for the SensorBase
   * @return Boolean indicating if the host is a valid sensorbase
   */
  public Boolean isHost(String host) {
    log("SDV checking isHost " + host + " as " + SensorBaseClient.isHost(host) + '\n');
    return SensorBaseClient.isHost(host);
  }

  /**
   * Returns true if the user and password is registered as a user with this host.
   * 
   * @param host the URI for the SensorBase
   * @param user user to authentication
   * @param password user password for authentication
   * @return Boolean indicating if the user and password are valid in the sensorbase
   */
  public Boolean isRegistered(String host, String user, String password) {
    log("SDV checking isRegistered, user: " + user + '\n');
    return SensorBaseClient.isRegistered(host, user, password);
  }

  /**
   * Registers the user with this host.
   * 
   * @param host the URI for the SensorBase
   * @param user user to authentication
   * @return Boolean indicating if the user and password are valid in the sensorbase
   */
  public Boolean registerUser(String host, String user) {
    Stopwatch stopwatch = new Stopwatch();
    stopwatch.start();  
    log("SDV registerUser on sensorbase (" + host + ") for (" + user + ")\n");
    Boolean result = Boolean.TRUE;
    try {
      log("SDV registerUser: invoking static method on client");
      SensorBaseClient.registerUser(host, user);
      log("SDV registerUser: returned from static method on client");
    }
    catch (SensorBaseClientException e) {
      result = Boolean.FALSE;
      log("SDV registerUser error: " + e.getMessage());
    }
    stopwatch.stop();
    log("SDV registerUser: exiting method with an execution time of " + stopwatch + '\n');
    return result;
  }
  
  /**
   * Returns the version number from the Manifest file.
   * 
   * @return String the version number from the Manifest file.
   */
  public String version() {
    String result = "Development";
    try {
      URL manifestURL = getServletContext().getResource("/META-INF/MANIFEST.MF");
      if (manifestURL == null) {
        log("SDV version: Unable to get Manifest file.\n");
        return result;
      }
      Manifest mf = new Manifest(manifestURL.openStream());
      Attributes attr = mf.getMainAttributes();
      result = attr.getValue("Implementation-Version");
      if (result == null) {
        log("SDV version: Unable to get 'Implementation-Version' attribute from the Manifest.\n");
        return result;
      }
    } 
    catch (Exception e) {
      log("SDV version error: " + e.getMessage() + '\n');
    }
    log("SDV version: Checking manifest, version=" + result + '\n');
    return result; 
  }
  
  /**
   * Returns the host uri from the configuration file.
   * 
   * @return String the host uri.
   */
  public String getHost() {
    String result = "http://hackystat.org:9876/sensorbase";
    try {
      this.sensorProperties = new Properties();
      if (this.sensorProperties == null) {
        log("SDV props: Unable to get SDV properties file.\n");
        return result;
      }
      result = this.sensorProperties.getHackystatHost();
      if (result == null) {
        log("SDV props: Unable to get 'Host' attribute from the properties file.\n");
        return result;
      }
    } 
    catch (Exception e) {
      log("SDV props error: " + e.getMessage() + '\n');
    }
    log("SDV props success: host=" + result + '\n');
    return result; 
  }
}
